#include "Paint.h"
#include <map>
#include <Windows.h>
#include <gdiplus.h>

static Gdiplus::GdiplusStartupInput gdipStartupInput = { 0 };
static ULONG_PTR gdipToken = 0;

class Paint::InnerImpl
{
public:
    Gdiplus::REAL width = 0, height = 0;
    uint32_t expand = 1;
    Gdiplus::Bitmap* bitmap = nullptr;
    Gdiplus::Graphics* graphics = nullptr;
    Gdiplus::SolidBrush* brushBack = nullptr;
    Gdiplus::SolidBrush* brushPtCircle = nullptr;
    Gdiplus::SolidBrush* brushPtCircleConvexHull = nullptr;
    Gdiplus::Pen* penLine = nullptr;
public:
    static int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
    {
        UINT  num = 0;          // number of image encoders
        UINT  size = 0;         // size of the image encoder array in bytes
        Gdiplus::ImageCodecInfo* pImageCodecInfo = NULL;
        Gdiplus::GetImageEncodersSize(&num, &size);
        if (size == 0)
            return -1;  // Failure
        pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));
        if (pImageCodecInfo == NULL)
            return -1;  // Failure
        Gdiplus::GetImageEncoders(num, size, pImageCodecInfo);
        for (UINT j = 0; j < num; ++j)
            if (wcscmp(pImageCodecInfo[j].MimeType, format) == 0)
            {
                *pClsid = pImageCodecInfo[j].Clsid;
                free(pImageCodecInfo);
                return j;  // Success
            }
        free(pImageCodecInfo);
        return -1;  // Failure
    }
};

//Paint* Paint::handle = nullptr;

Paint::Paint()
{
    inner = new InnerImpl;
}

Paint::~Paint()
{
    Release();
    delete inner;
    inner = nullptr;
}

/*Paint* Paint::Handle()
{
    if (!handle)
        handle = new Paint();
    return handle;
}*/

void Paint::InitializeGdiplus()
{
    Gdiplus::GdiplusStartup(&(gdipToken), &(gdipStartupInput), nullptr);
}

void Paint::TerminateGdiplus()
{
    Gdiplus::GdiplusShutdown(gdipToken);
}

void Paint::Initialize(const double_t& width, const double_t& height, const uint32_t& ex)
{
    //Gdiplus::GdiplusStartup(&(InnerImpl::gdipToken), &(InnerImpl::gdipStartupInput), nullptr);
    Release();
    inner->width = Gdiplus::REAL(width);
    inner->height = Gdiplus::REAL(height);
    inner->expand = ex;
    inner->bitmap = new Gdiplus::Bitmap(INT(width * ex + 0.5), INT(height * ex + 0.5));
    inner->graphics = new Gdiplus::Graphics(inner->bitmap);
    inner->graphics->SetSmoothingMode(Gdiplus::SmoothingMode::SmoothingModeHighQuality);
    inner->brushBack = new Gdiplus::SolidBrush(Gdiplus::Color(255, 255, 255));
    inner->brushPtCircle = new Gdiplus::SolidBrush(Gdiplus::Color(255, 0, 0));
    inner->brushPtCircleConvexHull = new Gdiplus::SolidBrush(Gdiplus::Color(0, 128, 0));
    inner->penLine = new Gdiplus::Pen(Gdiplus::Color(0, 0, 128), ex / 2.0f);
}

void Paint::Clear()
{
    if (inner->graphics)
        inner->graphics->FillRectangle(inner->brushBack, Gdiplus::RectF(0, 0, inner->width * inner->expand, inner->height * inner->expand));
}

void Paint::DrawPoint(const double_t& x, const double_t& y)
{
    if (inner->graphics)
    {
        Gdiplus::REAL dx = Gdiplus::REAL(x), dy = Gdiplus::REAL(y);
        Gdiplus::REAL r = inner->expand * sqrt(2.0f);
        inner->graphics->FillEllipse(inner->brushPtCircle, Gdiplus::RectF(dx * inner->expand - r / 2, dy * inner->expand - r / 2, r, r));
    }
}

void Paint::DrawPointConvexHull(const double_t& x, const double_t& y)
{
    if (inner->graphics)
    {
        Gdiplus::REAL dx = Gdiplus::REAL(x), dy = Gdiplus::REAL(y);
        Gdiplus::REAL r = inner->expand * sqrt(2.0f);
        inner->graphics->FillEllipse(inner->brushPtCircleConvexHull, Gdiplus::RectF(dx * inner->expand - r / 2, dy * inner->expand - r / 2, r, r));
    }
}

void Paint::DrawLine(const double_t& x1, const double_t& y1, const double_t& x2, const double_t& y2)
{
    if (inner->graphics)
    {
        Gdiplus::REAL dx1 = Gdiplus::REAL(x1), dy1 = Gdiplus::REAL(y1), dx2 = Gdiplus::REAL(x2), dy2 = Gdiplus::REAL(y2);
        inner->graphics->DrawLine(inner->penLine, Gdiplus::PointF(dx1 * inner->expand, dy1 * inner->expand), Gdiplus::PointF(dx2 * inner->expand, dy2 * inner->expand));
    }
}

void Paint::SetPixel(const int& x, const int& y, const unsigned char r, const unsigned char g, const unsigned char b)
{
    if (inner->graphics)
    {
        inner->bitmap->SetPixel(x, y, Gdiplus::Color(r, g, b));
    }
}

void Paint::Save(const wchar_t* filename, const wchar_t* type, const int& quality)
{
    if (inner->bitmap)
    {
        CLSID imgCLSID;
        InnerImpl::GetEncoderClsid(type, &imgCLSID);
        Gdiplus::EncoderParameters eps;
        eps.Count = 1;
        eps.Parameter[0].Guid = Gdiplus::EncoderQuality;
        eps.Parameter[0].Type = Gdiplus::EncoderParameterValueTypeLong;
        eps.Parameter[0].NumberOfValues = 1;
        int _quality = quality;
        eps.Parameter[0].Value = &_quality;
        inner->bitmap->Save(filename, &imgCLSID, &eps);
    }
}

void Paint::Release()
{
    delete inner->penLine;
    inner->penLine = nullptr;
    delete inner->brushPtCircleConvexHull;
    inner->brushPtCircleConvexHull = nullptr;
    delete inner->brushPtCircle;
    inner->brushPtCircle = nullptr;
    delete inner->brushBack;
    inner->brushBack = nullptr;
    delete inner->graphics;
    inner->graphics = nullptr;
    delete inner->bitmap;
    inner->bitmap = nullptr;
    //Gdiplus::GdiplusShutdown(inner->gdipToken);
    //delete handle;
    //handle = nullptr;
}